package com.tony.jfp.core;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.http.*;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.net.NetClientOptions;
import lombok.extern.slf4j.Slf4j;

/**
 * 服务器正向转发代理实现(http/https正向代理)
 */
@Slf4j
public class ForwardProxyVerticle extends AbstractVerticle {
    private final Vertx vertx;
    private final SecurityService securityService;
    private final Integer port;

    public ForwardProxyVerticle(Integer port, Vertx vertx, SecurityService securityService) {
        this.port = port;
        this.vertx = vertx;
        this.securityService = securityService;
    }

    /**
     * 代理监听服务
     */
    HttpServer server = null;
    /**
     * 客户端请求转发对象
     */
    HttpClient httpClient = null;

    /**
     * Windows 系统自带的“代理服务器”配置只显式支持http/https代理
     * 如果要支持socks，配置地址时前面加上：socks=ip，当前实现还不支持socks
     */
    @Override
    public void start() throws Exception {
        HttpClientOptions clientOptions = new HttpClientOptions().setKeepAlive(true);
        clientOptions.setConnectTimeout(1000);
        clientOptions.setMaxPoolSize(10);
        httpClient = vertx.createHttpClient(clientOptions);
        // 创建服务
        server = vertx.createHttpServer();
        server.requestHandler(request -> {
            request.pause();
            HostAndPort authority = request.authority();
            String hostPort = authority.toString();
            HttpServerResponse response = request.response();
            //https://www.cnblogs.com/wanghongwei-dev/p/18543399
            //客户端：发起请求的实体，它希望访问一个位于目标服务器上的资源。
            //代理服务器：位于客户端和目标服务器之间的中间实体，负责转发客户端的请求到目标服务器，并将目标服务器的响应返回给客户端。
            //目标服务器：提供客户端所需资源的服务器。
            //步骤
            //1.客户端发送 CONNECT 请求
            //客户端向代理服务器发送一个 CONNECT 请求。这个请求中包含了目标服务器的地址（如域名或 IP 地址）和端口号（通常是 HTTPS 服务的标准端口 443）。
            //例如，客户端可能发送一个请求来建立到 https://example.com 的连接，而 CONNECT 请求中实际包含的是 example.com 的 IP 地址和端口 443。
            //2.代理服务器处理 CONNECT 请求
            //代理服务器接收到 CONNECT 请求后，解析出目标服务器的地址和端口号。
            //代理服务器尝试建立一个到目标服务器的 TCP 连接。这个连接是在代理服务器和目标服务器之间建立的，而不是在客户端和目标服务器之间直接建立的。
            //3.TCP 连接建立
            //如果代理服务器成功建立了到目标服务器的 TCP 连接，它会返回一个 HTTP 200 状态码给客户端。
            //这个 HTTP 200 状态码是代理服务器对客户端 CONNECT 请求的响应，表示代理服务器已经成功建立了到目标服务器的连接。
            //4.数据传输
            //一旦 TCP 连接建立并且客户端收到了 HTTP 200 状态码，客户端和代理服务器之间的 HTTP 连接就被视为一个“隧道”。
            //在这个隧道中，客户端可以直接发送数据到代理服务器，而代理服务器会将这些数据原封不动地转发到目标服务器。
            //同样地，目标服务器的响应也会通过代理服务器原封不动地返回给客户端。
            //重要的是要注意，此时的数据传输不再遵循 HTTP 协议；相反，它是一个透明的 TCP 连接，可以传输任何类型的数据（包括 HTTPS 加密的数据）。
            //5.连接关闭
            //当客户端完成数据传输并希望关闭连接时，它会发送一个特殊的信号（如 TCP FIN 包）给代理服务器。
            //代理服务器接收到这个信号后，会关闭它与目标服务器之间的 TCP 连接。
            //同时，代理服务器也会向客户端发送一个响应（可能是 HTTP 响应或 TCP 关闭信号），表示连接已经关闭。
            if (canToNetSocket(request)) {
                NetClientOptions netClientOptions = new NetClientOptions().setSsl(request.isSSL()).setTcpKeepAlive(true).setConnectTimeout(1000);
                log.debug("tcp uri:{}", request.uri());
                log.debug("tcp authority:{}", authority);
                vertx.createNetClient(netClientOptions).connect(authority.port(), authority.host()).onSuccess(proxySocket -> {
                    log.debug("请求[{}]代理连接成功！", hostPort);
                    request.toNetSocket().onSuccess(socket -> {
                        log.debug("请求[{}]转为TCP成功！", hostPort);
                        socket.pause();
                        proxySocket.pause();
                        socket.closeHandler(handler -> {
                            proxySocket.closeHandler(null);
                            proxySocket.close();
                        });
                        proxySocket.closeHandler(handler -> {
                            socket.closeHandler(null);
                            socket.close();
                        });
                        socket.pipeTo(proxySocket);
                        proxySocket.pipeTo(socket);
                        socket.resume();
                        proxySocket.resume();
                    }).onFailure(error -> {
                        proxySocket.close();
                        if (!request.response().ended()) {
                            request.response().setStatusCode(500).end();
                        }
                        log.error("请求[{}]代理失败，{}！", hostPort, error.getMessage());
                    });
                }).onFailure(error -> {
                    if (!request.response().ended()) {
                        request.response().setStatusCode(500).end();
                    }
                    log.error("请求[{}]代理连接失败，{}！", hostPort, error.getMessage());
                });

            } else {
                RequestOptions options = new RequestOptions();
                log.debug("request uri:{}", request.uri());
                log.debug("authority:{}", authority);
                options.setMethod(request.method());
                options.setAbsoluteURI(request.uri());
                options.setHost(authority.host());
                options.setPort(authority.port() == -1 ? 80 : authority.port());
                //options.getHeaders().addAll(request.headers());
                HttpClient finalHttpClient = httpClient;
                httpClient.request(options).onSuccess(serverRequest -> {
                    log.debug("[{}]HTTP请求连接成功！", hostPort);
                    //GET functional.events.data.microsoft.com:443 HTTP/1.1
                    //Host: functional.events.data.microsoft.com:443
                    //Proxy-Connection: keep-alive
                    MultiMap headers = serverRequest.headers();
                    headers.addAll(request.headers());
                    serverRequest.send(request).onSuccess(serverResponse -> {
                        serverResponse.pause();
                        log.debug("[{}]HTTP代理请求返回成功！", hostPort);
                        response.headers().addAll(serverResponse.headers());
                        response.setStatusCode(serverResponse.statusCode());
                        response.send(serverResponse, result -> {
                            if (result.failed()) {
                                log.error("转发返回数据异常；{}", result.cause().getMessage());
                                if (!response.ended()) {
                                    response.setStatusCode(500).end(result.cause().getMessage());
                                }
                            }
                        });
                    }).onFailure(cause -> {
                        log.error("代理请求转发失败：{}", cause.getMessage());
                        if (!response.ended()) {
                            response.setStatusCode(500).end();
                        }
                    });
                }).onFailure(cause -> {
                    log.error("代理请求初始化失败：{}", cause.getMessage());
                    if (!response.ended()) {
                        response.setStatusCode(500).end();
                    }
                });
            }
        });
        server.listen(port, res -> {
            // 监听端口
            if (res.succeeded()) {
                log.info("正向代理服务启动成功，代理端口：{}。", port);
            } else {
                log.error("端口[{}]正向代理服务启动失败：{}", port, res.cause().getMessage());
            }
        });
    }

    /**
     * 判断是否可转为TCP请求
     * @param request 请求对象
     * @return true-可转为TCP请求，false-不可转为TCP请求
     */
    private static boolean canToNetSocket(HttpServerRequest request) {
        return request.method() == HttpMethod.CONNECT || (request.method() == HttpMethod.GET && request.headers().contains(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE, true));
    }

    @Override
    public void stop() {
        if (server != null) {
            server.close();
        }
        if (httpClient != null) {
            httpClient.close();
        }
    }
}
